// Copyright 2016 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT

#include <assert.h>
#include <err.h>
#include <lib/crypto/prng.h>
#include <pow2.h>
#include <string.h>
#include <zircon/compiler.h>
#include <zircon/types.h>

#include <explicit-memory/bytes.h>
#include <fbl/auto_call.h>
#include <kernel/mutex.h>
#include <ktl/atomic.h>

// See note in //zircon/third_party/ulib/uboringssl/rules.mk
#define BORINGSSL_NO_CXX
#include <openssl/chacha.h>
#include <openssl/sha.h>

namespace crypto {
namespace {

const uint128_t kNonceOverflow = ((uint128_t)1ULL) << 96;

}  // namespace

PRNG::PRNG(const void* data, size_t size) : PRNG(data, size, NonThreadSafeTag()) {
  static_assert(sizeof(key_) == SHA256_DIGEST_LENGTH, "Bad key length");
  BecomeThreadSafe();
}

PRNG::PRNG(const void* data, size_t size, NonThreadSafeTag tag) : nonce_(0), accumulated_(0) {
  memset(key_, 0, sizeof(key_));
  memset(&ready_, 0, sizeof(ready_));
  AddEntropy(data, size);
}

PRNG::~PRNG() {
  mandatory_memset(key_, 0, sizeof(key_));
  nonce_ = 0;
  event_destroy(&ready_);
}

void PRNG::AddEntropy(const void* data, size_t size) {
  DEBUG_ASSERT(data || size == 0);
  ASSERT(size <= kMaxEntropy);
  // Concurrent calls to |AddEntropy| must run sequentially.
  Guard<Mutex> guard(&mutex_);
  // Save the key on the stack, but guarantee we clean them up
  uint8_t key[sizeof(key_)];
  auto cleanup = fbl::MakeAutoCall([&] { mandatory_memset(key, 0, sizeof(key)); });
  // We mix all of the entropy with the previous key to make the PRNG state
  // depend on both the entropy added and the sequence in which it was added.
  SHA256_CTX ctx;
  SHA256_Init(&ctx);
  SHA256_Update(&ctx, data, size);
  {
    Guard<SpinLock, IrqSave> guard(&spinlock_);
    memcpy(key, key_, sizeof(key));
  }
  SHA256_Update(&ctx, key, sizeof(key));
  SHA256_Final(key, &ctx);
  {
    Guard<SpinLock, IrqSave> guard(&spinlock_);
    memcpy(key_, key, sizeof(key_));
  }
  // Increment how much entropy has been added, and signal if we have enough.
  if (is_thread_safe() && accumulated_.fetch_add(size) + size >= kMinEntropy) {
    event_signal(&ready_, true /* reschedule */);
  }
}

void PRNG::Draw(void* out, size_t size) {
  DEBUG_ASSERT(out || size == 0);
  ASSERT(size <= kMaxDrawLen);
  // Wait if other threads should add entropy.
  if (is_thread_safe() && accumulated_.load() < kMinEntropy) {
    event_wait(&ready_);
  }
  // Save these on the stack, but guarantee we clean them up
  uint8_t key[sizeof(key_)];
  uint128_t nonce;
  auto cleanup = fbl::MakeAutoCall([&] {
    mandatory_memset(key, 0, sizeof(key));
    nonce = 0;
  });
  {
    Guard<SpinLock, IrqSave> guard(&spinlock_);
    nonce = ++nonce_;
    memcpy(key, key_, sizeof(key));
  }
  ASSERT(nonce < kNonceOverflow);
  static_assert(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__, "must be LE");
  uint8_t* nonce_u8 = reinterpret_cast<uint8_t*>(&nonce);
  uint8_t* buf = reinterpret_cast<uint8_t*>(out);

  // We randomize |buf| by encrypting it with a key that is never exposed to
  // the caller, and a 96-bit nonce that changes on each call.  We don't zero
  // |buf| because the encrypted output meets the criteria of the PRNG
  // regardless of its original contents.  We reset the counter to 0 on each
  // request; it can't overflow because of the limit on the overall size.
  CRYPTO_chacha_20(buf, buf, size, key, nonce_u8, 0);
}

uint64_t PRNG::RandInt(uint64_t exclusive_upper_bound) {
  ASSERT(exclusive_upper_bound != 0);

  const uint log2 = log2_ulong_ceil(exclusive_upper_bound);
  const size_t mask =
      (log2 != sizeof(uint64_t) * CHAR_BIT) ? (uint64_t(1) << log2) - 1 : UINT64_MAX;
  DEBUG_ASSERT(exclusive_upper_bound - 1 <= mask);

  // This loop should terminate very fast, since the probability that the
  // drawn value is >= exclusive_upper_bound is less than 0.5.  This is the
  // classic discard out-of-range values approach.
  while (true) {
    uint64_t v;
    Draw(reinterpret_cast<uint8_t*>(&v), sizeof(uint64_t) / sizeof(uint8_t));
    v &= mask;
    if (v < exclusive_upper_bound) {
      return v;
    }
  }
}

// It is safe to call this function from PRNG's constructor provided
// |ready_| and |accumulated_| initialized.
void PRNG::BecomeThreadSafe() {
  ASSERT(!event_initialized(&ready_));
  event_init(&ready_, accumulated_.load() < kMinEntropy, 0);
}

bool PRNG::is_thread_safe() const {
  // Safe to read event.magic; it is read-only in a threaded context
  return event_initialized(&ready_);
}

}  // namespace crypto
